package network;

import hexgame.HexGameRequest;
import hexgame.HexMove;
import hexgame.HexPlayer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class HexNetwork {
	ServerSocket listeningSocket;
	
	/**
	 * Creates a new HexNetwork object and attempts to begin listening on the specified port
	 * @param port - the port to listen on
	 * @throws NetworkErrorException on a socket error when attempting to create the ServerSocket
	 */
	public HexNetwork(int port) throws NetworkErrorException {
		try {
			listeningSocket = new ServerSocket(port);
		}
		catch(IOException e) {
			throw new NetworkErrorException(e.getMessage());
		}
		
	}
	
	/**
	 * (Blocking) Get a game request. Waits until a client connects and attempts a handshake
	 * @return a HexGameRequest if request is valid
	 * @throws NetworkErrorException - If a socket error occurs. Stop calling if this happens
	 * @throws NetworkPartialClientException - If an error occurs during the handshake. Continue requesting new clients
	 */
	public HexGameRequest getClient() throws NetworkErrorException, NetworkPartialClientException {
		Socket socket;
		try {
			socket = listeningSocket.accept();
		}
		catch (IOException e) {
			throw new NetworkErrorException("Unable to get client on listening socket (" + listeningSocket.getLocalSocketAddress() + ") " + e.getMessage());
		}
		
		return performReceiveHandshake(socket);
	}
	/**
	 * Parses a move from the format (x, y).
	 * This function also flips x and y so that the red (local) player always goes from left to right.
	 * @param line - a string containing (x, y)
	 * @return a HexMove containing the coordinates and the player set to BLUE_REMOTE
	 */
	public static HexMove parseMove(String line)
	{
		if(line.charAt(0) != '(' || line.charAt(line.length()-1) != ')') {
			return null;
		}
		
		String[] parts = line.substring(1, line.length() - 1).split(", ");
		
		if(parts.length != 2) {
			System.out.println("Move invalid");
			return null;
		}
		int x = Integer.parseInt(parts[0]);
		int y = Integer.parseInt(parts[1]);
		
		// Flipped coordinates
		return new HexMove(y, x, HexPlayer.BLUE_REMOTE);
	}
	
	/**
	 * Format a position in the required way to send over the network. 
	 * @param x
	 * @param y
	 * @return The position formatted in a string
	 */
	public static String formatMove(int x, int y)
	{
		return "(" + x + ", " + y + ")";
	}
	
	/**
	 * Send a Hex game request to another client
	 * @param IP - The IP address or Hostname pointing to the other client
	 * @param port - The port number to connect to
	 * @param boardSize - The size of the board. Should be > 0 and if displayed on a UI < 11
	 * @param clientFirst - Do we want to play first?
	 * @return HexGameRequest - Valid if connection successful, and should be passed to the HexGame constructor. Otherise null.
	 * @throws UnknownHostException - The IP address string was not valid
	 * @throws NetworkPartialClientException - An error occured during connecting or handshaking
	 */
	public static HexGameRequest connectTo(String IP, int port, int boardSize, boolean clientFirst) throws UnknownHostException, NetworkPartialClientException {
		InetAddress addr;
		addr = InetAddress.getByName(IP);
		
		return connectTo(addr, port, boardSize, clientFirst);
	}
	
	private static HexGameRequest connectTo(InetAddress addr, int port, int boardSize, boolean clientFirst) throws NetworkPartialClientException {
		try {
			Socket socket = new Socket(addr, port);
			
			HexGameRequest request = HexNetwork.performSendHandshake(socket, boardSize, clientFirst);
			
			return request;
		} catch (IOException e) {
			throw new NetworkPartialClientException(e.getMessage());
		}
	}
	
	/**
	 * Performs the handshake when a socket has connected to the server.
	 * @param socket
	 * @return a HexGameRequest outlining the request details if successful
	 * @throws NetworkPartialClientException if an error occurs during the handshake
	 */
	private static HexGameRequest performReceiveHandshake(Socket socket) throws NetworkPartialClientException {
		try {
			PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			// Server: Hello
			writer.println("Hello");
			
			
			// Client: new_game, [board size], [client plays first (0, 1)]
			String input = reader.readLine();
			
			String[] parts = input.split(",");
			
			// less than 3 would allow us to implement extensions
			if(parts.length < 3) {
				socket.close();
				throw new NetworkPartialClientException("Invalid handshake");
			}
			
			if(!parts[0].equals("new_game")) {
				socket.close();
				throw new NetworkPartialClientException("Invalid handshake");
			}
			
			int boardSize = 0;
			try {
				boardSize = Integer.parseInt(parts[1].trim());
				
				// Whilst the largest board we should be able to display is 11x11, AI might want to work with larger board sizes
				if(boardSize < 1) {
					throw new NumberFormatException();
				}
			}
			catch(NumberFormatException e) {
				socket.close();
				throw new NetworkPartialClientException("Invalid handshake (invalid board size)");
			}
			
			int clientPlaysFirst = -1;
			try {
				clientPlaysFirst = Integer.parseInt(parts[2].trim());
				
				// Whilst the largest board we should be able to display is 11x11, AI might want to work with larger board sizes
				if(clientPlaysFirst != 1 && clientPlaysFirst != 0) {
					throw new NumberFormatException();
				}
			}
			catch(NumberFormatException e) {
				socket.close();
				throw new NetworkPartialClientException("Invalid handshake (invalid board size)");
			}
			
			// Server OK or Reject
			writer.println("OK");
			
			return new HexGameRequest(new RemotePlayer(socket, reader, writer), boardSize, clientPlaysFirst == 1, false);
		}
		catch(IOException e) {
			try {
				socket.close();
			}
			catch(IOException e2) {
				// Doesn't really matter if this fails here.
			}
			throw new NetworkPartialClientException(e.getMessage());
		}
	}
	
	/**
	 * Performs the handshake from the clients perspective.
	 * @param socket - The socket connected to a server
	 * @param boardSize - the boardSize to create the game for
	 * @param clientFirst - Do we want to play first?
	 * @return HexGameRequest if successful
	 * @throws NetworkPartialClientException - if an error occurs
	 */
	private static HexGameRequest performSendHandshake(Socket socket, int boardSize, boolean clientFirst) throws NetworkPartialClientException {
		try {
			PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			
			String message = reader.readLine();
			
			if(!message.equals("Hello")) {
				throw new NetworkPartialClientException("Server respondly invalidly to handshake request");
			}
			
			writer.println("new_game, " + boardSize + ", " + (clientFirst ? "1" : "0"));
			
			message = reader.readLine();
			
			if(message != null && message.equals("OK"))
			{
				return new HexGameRequest(new RemotePlayer(socket, reader, writer), boardSize, !clientFirst, true);
			}
			else if(message != null && message.equals("Reject")) {
				throw new NetworkPartialClientException("Server rejected request");
			}
			else {
				throw new NetworkPartialClientException("Server responded invalidly to new_game request");
			}
			
		} catch (IOException e) {
			throw new NetworkPartialClientException("A socket error occured whilst performing handshake");
		}
	}
}
